package com.kmxd.ams.app.arrange;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.kmxd.ams.app.arrange.command.*;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.client.archive.IArchiveFieldSvc;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.dto.ArchiveFieldQueryDTO;
import com.kmxd.ams.client.archive.vo.ArchiveFieldListVO;
import com.kmxd.ams.client.archive.vo.ArchiveTypeVO;
import com.kmxd.ams.client.arrange.IBoxSvc;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.IVolumeSvc;
import com.kmxd.ams.client.arrange.dto.*;
import com.kmxd.ams.client.arrange.vo.CheckEfileVO;
import com.kmxd.ams.client.arrange.vo.UploadImportDataVO;
import com.kmxd.ams.client.arrange.vo.UploadImportVO;
import com.kmxd.ams.client.event.copy.CopyDocumentEvent;
import com.kmxd.ams.client.event.efile.DocFileNumEvent;
import com.kmxd.ams.client.event.es.EsRemoveDataEvent;
import com.kmxd.ams.client.event.es.EsSaveDataEvent;
import com.kmxd.ams.client.event.es.EsUpdateDataEvent;
import com.kmxd.ams.client.event.log.DocumentLogEvent;
import com.kmxd.ams.client.event.summary.SummaryBoxEvent;
import com.kmxd.ams.client.event.summary.SummaryProjectEvent;
import com.kmxd.ams.client.event.summary.SummaryVolumeEvent;
import com.kmxd.ams.client.logs.ILogArchiveSvc;
import com.kmxd.ams.client.oss.IEfileRecordSvc;
import com.kmxd.ams.client.oss.dto.EfileRecordDTO;
import com.kmxd.ams.client.oss.vo.EfileRecordListVO;
import com.kmxd.ams.client.oss.vo.EfileRecordVO;
import com.kmxd.ams.core.common.bo.LoginUser;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.constant.ArchControlConst;
import com.kmxd.ams.core.common.constant.Constants;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.*;
import com.kmxd.ams.core.common.query.QueryGen;
import com.kmxd.ams.core.config.AmsConfig;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.exception.ParamsValidateException;
import com.kmxd.ams.core.exception.ReturnDataException;
import com.kmxd.ams.core.oss.template.OssTemplate;
import com.kmxd.ams.core.util.*;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.arrange.base.BaseBO;
import com.kmxd.ams.infra.arrange.entity.Box;
import com.kmxd.ams.infra.arrange.entity.Document;
import com.kmxd.ams.infra.arrange.entity.Volume;
import com.kmxd.ams.infra.arrange.mapper.DocumentMapper;
import java.util.*;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.slf4j.MDC;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 文件级 服务实现类
 *
 * @author ZuoLG
 * @since 2022-07-20
 */
@Service
@Slf4j
@AllArgsConstructor
public class DocumentSvcImpl extends ServiceImpl<DocumentMapper, Document> implements IDocumentSvc {

  private DocumentMapper documentMapper;
  private AmsEventPublisher amsEventPublisher;
  private IEfileRecordSvc efileRecordSvc;
  private OssTemplate ossTemplate;
  private IArchiveFieldSvc archiveFieldSvc;
  private CommonQueryCmd commonQueryCmd;
  private IArchiveTypeSvc archiveTypeSvc;
  private RetentionCmd retentionCmd;
  private final ILogArchiveSvc logArchiveSvc;
  private final DataEmptyCheckCmd dataEmptyCheckCmd;
  private JdbcTemplate jdbcTemplate;
  private AmsConfig amsConfig;
  private AdvancedQueryCmd queryCmd;
  private final ImportDateCheckCmd dateCheckCmd;

  @Override
  public Page<Document> selectPage(Page reqPage, Document req) {
    if (NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY)) {
      return documentMapper.selectDelByPage(reqPage, req.getTableId(), req);
    }
    // 此代码不能放到查询回收站之前
    req.setStatus(NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY) ? null : req.getStatus());
    QueryWrapper<Document> wrapper = QueryGen.init(new QueryWrapper<Document>(), req);
    buildSearchWrapper(req, wrapper);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));
    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    String filterSql = queryCmd.getDocQuerySql(req);
    String sql =
        StrUtil.format(
            "{}  {}",
            StrUtil.isBlank(dataConditionSql) ? "" : dataConditionSql,
            StrUtil.isBlank(filterSql) ? "" : filterSql);
    if (CharSequenceUtil.isNotBlank(sql)) {
      wrapper.apply(sql);
    }

    LambdaQueryWrapper<Document> queryWrapper = wrapper.lambda();

    if (BooleanUtil.isTrue(req.getOutArch())) {
      queryWrapper.gt(Document::getStorageNum, 0);
    }

    if (BooleanUtil.isTrue(req.getScatteredStatus())) {
      queryWrapper.isNull(Document::getBoxId);
      queryWrapper.isNull(Document::getVolId);
    }
    Set<Long> docIds = IdUtil.split(req.getDocIds());
    if (ObjectUtil.isNotEmpty(docIds)) {
      queryWrapper.in(Document::getId, docIds);
    }
    Set<Long> volIds = IdUtil.split(req.getVolIds());
    if (ObjectUtil.isNotEmpty(volIds)) {
      queryWrapper.in(Document::getVolId, volIds);
    }

    Set<Long> boxIds = IdUtil.split(req.getBoxIds());
    if (ObjectUtil.isNotEmpty(boxIds)) {
      queryWrapper.in(Document::getBoxId, boxIds);
    }

    if (ObjectUtil.isEmpty(req.getControlIdentifier())) {
      List<Integer> ctls = com.google.common.collect.Lists.newArrayList(ArchControlConst.CTL_1,ArchControlConst.CTL_2,ArchControlConst.CTL_3);
      queryWrapper.in(Document::getControlIdentifier,ctls);
    }
    return documentMapper.selectPage(reqPage, queryWrapper);
  }

  @Override
  public List<Document> selectList(Document req) {
    req.setStatus(
        ObjectUtil.isNotEmpty(req.getStatus())
                && NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY)
            ? null
            : req.getStatus());
    QueryWrapper<Document> wrapper = QueryGen.init(new QueryWrapper<Document>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));

    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    LambdaQueryWrapper<Document> queryWrapper = wrapper.lambda();
    if (BooleanUtil.isTrue(req.getOutArch())) {
      queryWrapper.gt(Document::getStorageNum, 0);
    }
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }
    Set<Long> ids = IdUtil.split(req.getDocIds());
    if (ObjectUtil.isNotEmpty(ids)) {
      queryWrapper.in(Document::getId, ids);
    }
    Set<Long> volIds = IdUtil.split(req.getVolIds());
    if (ObjectUtil.isNotEmpty(volIds)) {
      queryWrapper.in(Document::getVolId, volIds);
    }

    Set<Long> boxIds = IdUtil.split(req.getBoxIds());
    if (ObjectUtil.isNotEmpty(boxIds)) {
      queryWrapper.in(Document::getBoxId, boxIds);
    }

    return documentMapper.selectList(queryWrapper);
  }

  @Override
  public List<Document> selectListWithNoDataScope(Document req) {
    req.setStatus(
        ObjectUtil.isNotEmpty(req.getStatus())
                && NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY)
            ? null
            : req.getStatus());
    QueryWrapper<Document> wrapper = QueryGen.init(new QueryWrapper<Document>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()),
        "ascend".equals(req.getOrder()),
        StringUtils.camelToUnderline(req.getField()));

    commonQueryCmd.buildFieldOrderQuery(req, wrapper);
    LambdaQueryWrapper<Document> queryWrapper = wrapper.lambda();
    // 设置数据过滤条件
    String dataConditionSql = QueryUtil.buildDataSql(req.getDataCondition());
    if (CharSequenceUtil.isNotBlank(dataConditionSql)) {
      wrapper.apply(dataConditionSql);
    }
    Set<Long> ids = IdUtil.split(req.getDocIds());
    if (ObjectUtil.isNotEmpty(ids)) {
      queryWrapper.in(Document::getId, ids);
    }
    Set<Long> volIds = IdUtil.split(req.getVolIds());
    if (ObjectUtil.isNotEmpty(volIds)) {
      queryWrapper.in(Document::getVolId, volIds);
    }

    Set<Long> boxIds = IdUtil.split(req.getBoxIds());
    if (ObjectUtil.isNotEmpty(boxIds)) {
      queryWrapper.in(Document::getBoxId, boxIds);
    }

    return documentMapper.selectListNoDataScope(queryWrapper);
  }

  @Override
  public List<Document> listNoDataScope(Wrapper<Document> wrapper) {
    return documentMapper.listNoDataScope(wrapper);
  }

  @Override
  public List<Map<String, Object>> selectExport(DataExportDTO req) {
    req.setStatus(
        ObjectUtil.isNotEmpty(req.getStatus())
                && NumberUtil.equals(req.getStatus(), Constants.TRASH_KEY)
            ? null
            : req.getStatus());
    QueryWrapper<Document> wrapper = QueryGen.init(new QueryWrapper<Document>(), req);
    wrapper.orderBy(
        StrUtil.isNotBlank(req.getField()), "ascend".equals(req.getOrder()), req.getField());

    String[] fields =
        req.getFields().stream()
            .filter(item -> ObjectUtil.isNotEmpty(item.getDbFieldKey()))
            .map(item -> item.getDbFieldKey())
            .toArray(String[]::new);
    wrapper.select(fields);
    LambdaQueryWrapper<Document> queryWrapper = wrapper.lambda();
    Set<Long> ids = IdUtil.split(req.getDocIds());
    if (ObjectUtil.isNotEmpty(ids)) {
      queryWrapper.in(Document::getId, ids);
    }
    return documentMapper.selectMaps(queryWrapper);
  }

  @Override
  public List<Document> selectListByDay(DocumentDayQueryDTO req) {
    LambdaQueryWrapper<Document> queryWrapper =
        QueryGen.init(new QueryWrapper<Document>(), req).lambda();
    queryWrapper.ge(Document::getUpdateTime, req.getStartDate());
    queryWrapper.le(Document::getUpdateTime, req.getEndDate());
    queryWrapper.eq(Document::getStatus, ArchStatusEnum.S1.getCode());
    return documentMapper.selectList(queryWrapper);
  }

  @Override
  public List<Document> selectListByIds(BaseBO req) {
    if (StrUtil.isBlank(req.getDocIds())) {
      return Lists.newArrayList();
    }
    LambdaQueryWrapper<Document> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.in(Document::getId, IdUtil.split(req.getDocIds()));
    queryWrapper.orderByAsc(Document::getDocSequence);
    return documentMapper.selectList(queryWrapper);
  }

  @Override
  public List<Document> selectListByBoxIds(BaseBO req, boolean have) {
    if (ObjectUtil.isEmpty(req.getBoxIds())) {
      throw BizTipException.instance(ErrorCode.QUICK_DATA_CHECK_ERROR, "盒ID选择为空！");
    }
    LambdaQueryWrapper<Document> queryWrapper = Wrappers.lambdaQuery();
    if (!have) {
      queryWrapper.notIn(Document::getBoxId, IdUtil.split(req.getBoxIds()));
    } else {
      queryWrapper.in(Document::getBoxId, IdUtil.split(req.getBoxIds()));
    }
    queryWrapper.orderByAsc(Document::getDocSequence);
    return documentMapper.selectList(queryWrapper);
  }

  @Override
  public List<Document> selectListByVolIds(BaseBO req, boolean have) {
    if (ObjectUtil.isEmpty(req.getVolIds())) {
      throw BizTipException.instance(ErrorCode.QUICK_DATA_CHECK_ERROR, "案卷ID选择为空！");
    }
    LambdaQueryWrapper<Document> queryWrapper = Wrappers.lambdaQuery();
    if (!have) {
      queryWrapper.notIn(Document::getVolId, IdUtil.split(req.getVolIds()));
    } else {
      queryWrapper.in(Document::getVolId, IdUtil.split(req.getVolIds()));
    }
    queryWrapper.orderByAsc(Document::getDocSequence);
    return documentMapper.selectList(queryWrapper);
  }

  @Override
  public List<Document> selectListByProjIds(BaseBO req, boolean have) {
    if (ObjectUtil.isEmpty(req.getProjIds())) {
      throw BizTipException.instance(ErrorCode.QUICK_DATA_CHECK_ERROR, "项目ID选择为空！");
    }
    LambdaQueryWrapper<Document> queryWrapper = Wrappers.lambdaQuery();
    if (!have) {
      queryWrapper.notIn(Document::getProjId, IdUtil.split(req.getProjIds()));
    } else {
      queryWrapper.in(Document::getProjId, IdUtil.split(req.getProjIds()));
    }
    return documentMapper.selectList(queryWrapper);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void add(Document req) {
    checkData(req);
    if (ObjectUtil.isEmpty(req.getId())) {
      req.setId(IdWorker.getId());
    }
    // 数据为空校验
    dataEmptyCheckCmd.checkDataEmpty(req);
    // 设置默认值
    req.setDepartmentId(
        ObjectUtil.isNotEmpty(req.getDepartmentId())
            ? req.getDepartmentId()
            : SecurityUtil.getLoginUser().getDeptId());
    req.setDepartment(
        ObjectUtil.isNotEmpty(req.getDepartment())
            ? req.getDepartment()
            : SecurityUtil.getLoginUser().getDeptName());
    req.setDelFlag(DelFlagEnum.NO.getCode());
    //    if (StrUtil.isBlank(req.getArchCode()) || Objects.isNull(req.getDocSequence())) {
    //      req.setDocSequence(null);
    //      req.setArchCode(null);
    //    }
    req.setManageStatus(SecurityUtil.getManageStatus());

    // 判断成文日期，计算到期时间
    if (ObjectUtil.isNotEmpty(req.getDocDate())) {
      req.setExpireYear(
          retentionCmd.getExpireYear(req.getYear(), req.getRetentionPeriod(), req.getDocDate()));
    }

    this.saveOrUpdate(req);

    // 更新电子文件数量
    BaseBO baseBO = BeanUtil.copyProperties(req, BaseBO.class);
    baseBO.setDocIds(req.getId().toString());
    amsEventPublisher.publishEvent(new DocFileNumEvent(baseBO));
    summary(req);
    MDC.put(ArchConst.UPDATE_DATA + req.getId(), JSONUtil.toJsonStr(req));
    MDC.put(ArchConst.USE_DATA + req.getId(), JSONUtil.toJsonStr(req));
    // 更新数据到es
    if (ObjectUtil.equals(req.getStatus(), ArchStatusEnum.S1.getCode())) {
      amsEventPublisher.publishEvent(
          new EsSaveDataEvent(
              ImmutableList.of(req.getId()), req.getArchTypeId(), req.getTableId()));
    }
  }

  /**
   * 汇总信息
   *
   * @param req
   */
  @Override
  public void summary(Document req) {

    ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());
    // 案卷
    if (archiveType.getHaveVol() && ObjectUtil.isNotEmpty(req.getVolId())) {
      amsEventPublisher.publishEvent(
          new SummaryVolumeEvent(
              req.getVolId(),
              req.getArchTypeId(),
              archiveType.getVolTableId(),
              archiveType.getDocTableId()));
    }

    // 盒
    if (archiveType.getHaveBox() && ObjectUtil.isNotEmpty(req.getBoxId())) {
      amsEventPublisher.publishEvent(
          new SummaryBoxEvent(
              req.getBoxId(),
              req.getArchTypeId(),
              archiveType.getBoxTableId(),
              archiveType.getDocTableId()));
    }
  }

  public void checkData(Document req) {
    if (ObjectUtil.isNull(req.getStatus())) {
      throw BizTipException.instance(ErrorCode.ARRANGE_STATUS_CAN_NOT_NULL, "档案状态不能为空");
    }
    if (!ImmutableSet.of(ArchStatusEnum.S0.getCode(), ArchStatusEnum.S1.getCode())
        .contains(req.getStatus())) {
      return;
    }
    ArchiveFieldQueryDTO qry = new ArchiveFieldQueryDTO();
    qry.setTableId(req.getTableId());
    if (ArchStatusEnum.S0.getCode().equals(req.getStatus())) {
      qry.setAttrRepeatWarn(Boolean.TRUE);
    }
    if (ArchStatusEnum.S1.getCode().equals(req.getStatus())) {
      qry.setAttrRepeatWarnArrange(Boolean.TRUE);
    }
    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectList(qry);
    if (ObjectUtil.isEmpty(fieldList)) {
      return;
    }
    fieldList.forEach(
        item -> {
          QueryWrapper<Document> query = Wrappers.query();
          query
              .lambda()
              .ne(Objects.nonNull(req.getId()), Document::getId, req.getId())
              .eq(Document::getStatus, req.getStatus());
          query.eq(
              item.getDbFieldKey(),
              JSONUtil.parseObj(req).get(StringUtils.underlineToCamel(item.getDbFieldKey())));
          Long count = documentMapper.selectCount(query);
          long flag = null == count ? 0 : count;
          if (flag > 0) {
            throw ParamsValidateException.instance(
                StringUtils.camelToUnderline(item.getDbFieldKey()), ParamsValidateType.DATA_REPEAT);
          }
        });
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(Document req) {
    if (Objects.nonNull(req.getId())) {
      checkData(req);
      // 数据为空校验
      dataEmptyCheckCmd.checkDataEmpty(req);
      //      if (StrUtil.isBlank(req.getArchCode()) || Objects.isNull(req.getDocSequence())) {
      //        req.setDocSequence(null);
      //        req.setArchCode(null);
      //      }

      req.setFourCheckId(null);
      req.setFourCheckResult(null);
      // 判断成文日期，计算到期时间
      if (ObjectUtil.isNotEmpty(req.getDocDate())) {
        req.setExpireYear(
            retentionCmd.getExpireYear(req.getYear(), req.getRetentionPeriod(), req.getDocDate()));
      }

      req.setUpdateBy(
          ObjectUtil.isNotEmpty(SecurityUtil.getLoginUser())
              ? SecurityUtil.getUserId()
              : req.getUpdateBy());
      req.setUpdateTime(new Date());
      // 修改时把固定修改字段过滤处理
      documentMapper.updateById(req);
      // 更新电子文件数量
      BaseBO baseBO = BeanUtil.copyProperties(req, BaseBO.class);
      baseBO.setDocIds(req.getId().toString());
      amsEventPublisher.publishEvent(new DocFileNumEvent(baseBO));
      // 发布创建数据目录事件
      summary(req);
      // 更新电子文件数量
      MDC.put(ArchConst.UPDATE_DATA + req.getId(), JSONUtil.toJsonStr(req));
      // 更新数据到es
      if (ObjectUtil.equals(req.getStatus(), ArchStatusEnum.S1.getCode())) {
        amsEventPublisher.publishEvent(
            new EsUpdateDataEvent(
                ImmutableList.of(req.getId()), req.getArchTypeId(), req.getTableId()));
      }
    } else {
      List<Document> documents = selectListByIds(req);
      if (ObjectUtil.isEmpty(documents)) {
        throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_NOT_EXIST, "当前数据不存在！");
      }
      // 当前这个功能基本为调整分组使用,校验是否存在组卷 装盒的情况
      long volCount =
          documents.stream().filter(item -> ObjectUtil.isNotEmpty(item.getVolId())).count();
      if (volCount > 0) {
        throw BizTipException.instance(ErrorCode.ARCH_IN_VOL, "所选档案中包含已组卷的档案,无法执行此操作,请重新选择");
      }
      long boxCount =
          documents.stream().filter(item -> ObjectUtil.isNotEmpty(item.getBoxId())).count();
      if (boxCount > 0) {
        throw BizTipException.instance(ErrorCode.ARCH_IN_BOX, "所选档案中包含已装盒的档案,无法执行此操作,请重新选择");
      }

      documents.forEach(
          item -> {
            Document source = ConvertUtil.toBean(item, Document.class);
            source.setArchTypeId(req.getArchTypeId());
            source.setTableId(req.getTableId());
            BeanUtil.copyProperties(req, item, CopyOptions.create().ignoreNullValue());
            if (CharSequenceUtil.isNotBlank(req.getClassfy())) {
              source.setClassfy(req.getClassfy());
            }
            if (CharSequenceUtil.isNotBlank(req.getClassfyName())) {
              source.setClassfyName(req.getClassfyName());
            }
            if (CharSequenceUtil.isNotBlank(req.getRetentionPeriod())) {
              source.setRetentionPeriod(req.getRetentionPeriod());
            }
            if (CharSequenceUtil.isNotBlank(req.getSecurity())) {
              source.setSecurity(req.getSecurity());
            }
            item.setArchTypeId(req.getArchTypeId());
            item.setTableId(req.getTableId());
            // 调整分组，设置重置档号
            item.setDocSequence(null);
            item.setArchCode(null);
            req.setFourCheckId(null);
            req.setFourCheckResult(null);
          });
      updateBatchById(documents);

      amsEventPublisher.publishEvent(new DocumentLogEvent(req, ArchOpEnum.BATCH_UPDATE, "批量修改"));
    }
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(Document req) {
    List<Document> list = this.selectListByIds(req);
    Set<Long> ids = IdUtil.split(req.getDocIds());
    this.baseMapper.deleteBatchIds(ids);
    list.forEach(
        document -> {
          document.setDelFlag(DelFlagEnum.YES.getCode());
          document.setArchTypeId(req.getArchTypeId());
          document.setTableId(req.getTableId());
          try {
            summary(document);
          } catch (Exception e) {
            log.error(e.getMessage(), e);
          }
        });
    amsEventPublisher.publishEvent(new DocumentLogEvent(req, ArchOpEnum.DELETE, "删除数据到回收站"));
    // 删除es数据
    amsEventPublisher.publishEvent(
        new EsRemoveDataEvent(req.getDocIds(), req.getArchTypeId(), req.getTableId()));
  }

  @Override
  public Document view(Document req) {
    Document document = documentMapper.selectById(req.getId());
    if (ObjectUtil.isNotNull(document)) {
      MDC.put(ArchConst.USE_DATA + document.getId(), JSONUtil.toJsonStr(document));
    }
    return document;
  }

  @Override
  public Document viewIgnoreDelFlag(Document req) {
    QueryUtil.checkSql(req.getTableId());
    Document document = documentMapper.selectOneIgnoreDelFlag(req.getTableId(), req.getId());
    return document;
  }

  @Override
  public boolean verify(Document req) {
    // 判断是否同一分类 ,拿到档案ids判断是否同一分组
    List<Document> docList = selectList(req);
    // 校验是否在同一分组
    Map<String, List<Document>> collect =
        docList.stream()
            .peek(
                item -> {
                  if (ObjectUtil.isNull(item.getClassfy())) {
                    item.setClassfy("");
                  }
                })
            .collect(Collectors.groupingBy(Document::getClassfy));

    return collect.keySet().size() <= 1;
  }

  @Override
  public long count(Document req) {
    QueryWrapper<Document> query = Wrappers.query();
    QueryGen.init(query, req);
    buildSearchWrapper(req, query);
    if (BooleanUtil.isTrue(req.getOutArch())) {
      query.ge(FieldUtil.getColumn(Document::getStorageNum), 0);
    }
    Long count = documentMapper.selectCount(query);
    return null == count ? 0 : count;
  }

  private void buildSearchWrapper(Document req, QueryWrapper<Document> query) {
    if (StrUtil.isBlank(req.getKeywords())) {
      return;
    }
    ArchiveFieldQueryDTO qryField = new ArchiveFieldQueryDTO();
    qryField.setArchTypeId(req.getArchTypeId());
    qryField.setTableId(req.getTableId());
    qryField.setEsIndex(true);

    List<ArchiveFieldListVO> fieldList = archiveFieldSvc.selectList(qryField);
    // 过滤的list为空，会报空指针异常
    List<String> fields =
        fieldList.stream().map(ArchiveFieldListVO::getDbFieldKey).collect(Collectors.toList());
    QueryUtil.buildSearchWrapper(req.getKeywords(), fields, query);
  }

  @Override
  public void copy(DocumentCopyDTO req) {
    if (StrUtil.isBlank(req.getDocIds())) {
      return;
    }
    Set<Long> ids = IdUtil.split(req.getDocIds());
    ids.forEach(
        id -> {
          DocumentCopyDTO dto = ConvertUtil.toBean(req, DocumentCopyDTO.class);
          dto.setId(id);
          amsEventPublisher.publishEvent(new CopyDocumentEvent(dto));
        });
  }

  @Override
  public boolean removeAllEfile(BaseBO req) {
    boolean flag = false;
    List<Document> listDocs = selectListByIds(req);
    Set<Long> docIds = new HashSet<>();
    List<Document> docBatch =
        listDocs.stream()
            .peek(
                item -> {
                  item.setArchTypeId(req.getArchTypeId());
                  item.setTableId(req.getTableId());
                  item.setFilesNum(0);
                  docIds.add(item.getId());
                })
            .collect(Collectors.toList());
    efileRecordSvc.removeByDocIds(docIds);
    flag = updateBatchById(docBatch);
    return flag;
  }

  @Override
  public List<CheckEfileVO> checkEfile(BaseBO req) {
    List<Document> listDocs = selectListByIds(req);
    List<CheckEfileVO> list = Lists.newArrayList();
    Set<Long> ids = listDocs.stream().map(Document::getId).collect(Collectors.toSet());
    List<EfileRecordListVO> efileLists =
        efileRecordSvc.selectListByDocIds(req.getArchTypeId(), ids);
    listDocs.forEach(
        item -> {
          List<EfileRecordListVO> efiles =
              efileLists.stream()
                  .filter(file -> file.getArchId().equals(item.getId()))
                  .collect(Collectors.toList());
          if (!efiles.isEmpty()) {
            efiles.forEach(
                efile -> {
                  CheckEfileVO checkEfileVO = BeanUtil.copyProperties(item, CheckEfileVO.class);
                  checkEfileVO.setEfileName(efile.getSaveFileName());
                  boolean flag =
                      ossTemplate.exist(ossTemplate.getPath(item.getStatus()) + efile.getPath());
                  checkEfileVO.setCheckResult(
                      flag ? OssStatusEnum.NORMAL.getName() : OssStatusEnum.ABNORMAL.getName());
                  checkEfileVO.setCheckStatus(
                      flag ? OssStatusEnum.NORMAL.getCode() : OssStatusEnum.ABNORMAL.getCode());
                  checkEfileVO.setFileSize(efile.getFileSize());
                  list.add(checkEfileVO);
                });
          }
        });
    return list;
  }

  @Override
  public String getSequence(Map<String, Object> columnMap, String tableId) {
    QueryWrapper<Document> queryWrapper = Wrappers.query();
    columnMap.entrySet().stream()
        .filter(
            item ->
                !ImmutableSet.of(ArchConst.DOC_IDS, ArchConst.ARCH_TYPE_ID, ArchConst.TABLE_ID)
                    .contains(item.getKey()))
        .forEach(
            entry ->
                queryWrapper.eq(
                    StringUtils.isCamel(entry.getKey())
                        ? StringUtils.camelToUnderline(entry.getKey())
                        : entry.getKey(),
                    entry.getValue()));
    Integer sequence = null;
    queryWrapper.orderByDesc(ArchConst.DOC_SEQUENCE_DB);
    queryWrapper.last("LIMIT 0, 1");
    queryWrapper.in(
        ArchConst.DEF_STATUS,
        ImmutableSet.of(ArchStatusEnum.S0.getCode(), ArchStatusEnum.S1.getCode()));
    Object docIds = columnMap.get(ArchConst.DOC_IDS);
    if (Objects.nonNull(docIds)) {
      Set<Long> ids = IdUtil.split(String.valueOf(Optional.ofNullable(docIds).orElse("")));
      if (ObjectUtil.isNotEmpty(ids)) {
        queryWrapper.notIn(ArchConst.DEF_ID, ids);
      }
    }
    Object volIds = columnMap.get(ArchConst.VOL_ID);
    if (Objects.nonNull(volIds)) {
      Set<Long> ids = IdUtil.split(String.valueOf(Optional.ofNullable(volIds).orElse("")));
      if (ObjectUtil.isNotEmpty(ids)) {
        queryWrapper.notIn(ArchConst.VOL_ID, ids);
      }
    }

    DynamicTableNameUtil.setTableName(tableId);
    List<Document> docList = documentMapper.selectListNoDataScope(queryWrapper);
    if (!docList.isEmpty()) {
      sequence =
          Optional.ofNullable(docList.get(0).getDocSequence())
              .orElse(docList.get(0).getDocSequence());
    }
    return NumberUtil.toStr(sequence, "0");
  }

  @Override
  public Object selectWithSummaryType(Document req, String dbFieldKey, SummaryTypeEnum type) {
    String fieldName = StringUtils.underlineToCamel(dbFieldKey);
    QueryWrapper<Document> wrapper =
        Wrappers.<Document>query()
            .select(String.format("%s(%s) as %s", type.getFunc(), dbFieldKey, fieldName))
            .eq(
                Objects.nonNull(req.getBoxId()),
                FieldUtil.getColumn(Document::getBoxId),
                req.getBoxId())
            .eq(
                Objects.nonNull(req.getVolId()),
                FieldUtil.getColumn(Document::getVolId),
                req.getVolId());
    Document document = documentMapper.selectOne(wrapper);
    Map<String, Object> map = BeanUtil.beanToMap(document);
    if (Objects.nonNull(map) && map.containsKey(fieldName)) {
      return map.get(fieldName);
    } else {
      return null;
    }
  }

  @Override
  public Integer getMaxDocSequence(
      Map<String, Object> columnMap, String tableId, boolean onlyArchived) {
    QueryWrapper<Document> queryWrapper = Wrappers.query();
    columnMap.entrySet().stream()
        .filter(
            item ->
                !ImmutableSet.of(ArchConst.DOC_IDS, ArchConst.ARCH_TYPE_ID, ArchConst.TABLE_ID)
                    .contains(item.getKey()))
        .forEach(
            entry ->
                queryWrapper.eq(
                    StringUtils.isCamel(entry.getKey())
                        ? StringUtils.camelToUnderline(entry.getKey())
                        : entry.getKey(),
                    entry.getValue()));
    queryWrapper.orderByDesc("doc_sequence");
    queryWrapper.last("LIMIT 0, 1");
    if (onlyArchived) {
      queryWrapper.eq(ArchConst.DEF_STATUS, ArchStatusEnum.S1.getCode());
    } else {
      queryWrapper.in(
          ArchConst.DEF_STATUS,
          ImmutableSet.of(ArchStatusEnum.S0.getCode(), ArchStatusEnum.S1.getCode()));
    }
    DynamicTableNameUtil.setTableName(tableId);
    List<Document> docList = documentMapper.selectListNoDataScope(queryWrapper);
    if (null == docList || docList.isEmpty()) {
      return 1;
    }
    Document document = docList.get(0);
    Integer sequence = Optional.ofNullable(document.getDocSequence()).orElse(0);
    return sequence + 1;
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public void detach(DetachDTO req) {
    List<Document> documentList = com.google.common.collect.Lists.newArrayList();
    DynamicTableNameUtil.setTableName(
        StrUtil.isNotBlank(req.getDocTableId()) ? req.getDocTableId() : req.getTableId());
    if (ObjectUtil.isNotEmpty(req.getDocIds())) {
      documentList = selectListByIds(req);
    }
    if (ObjectUtil.isNotEmpty(req.getVolIds())) {
      documentList = selectListByVolIds(req, true);
    }
    if (ObjectUtil.isNotEmpty(req.getBoxIds())) {
      documentList = selectListByBoxIds(req, true);
    }
    if (ObjectUtil.isNotEmpty(req.getProjIds())) {
      documentList = selectListByProjIds(req, true);
    }
    if (ObjectUtil.isEmpty(documentList)) {
      return;
    }
    DetachEnum type = DetachEnum.convert(req.getType());
    List<Long> parentIds = Lists.newArrayList();
    ArchOpEnum opEnum =
        ObjectUtil.equals(type, DetachEnum.VOLUME)
            ? ArchOpEnum.DETACH_FROM_VOLUME
            : ObjectUtil.equals(type, DetachEnum.BOX)
                ? ArchOpEnum.UN_BOXING
                : ArchOpEnum.DETACH_FROM_PROJECT;
    documentList.forEach(
        document -> {
          switch (type) {
            case VOLUME:
              parentIds.add(document.getVolId());
              document.setArchCode(null);
              document.setDocSequence(null);
              document.setVolId(null);
              document.setVolArchCode(null);
              return;
            case BOX:
              parentIds.add(document.getBoxId());
              document.setBoxId(null);
              document.setBoxNo(null);
              return;
            case PROJ:
              parentIds.add(document.getProjId());
              document.setProjId(null);
              return;
            default:
          }
          document.setArchTypeId(req.getArchTypeId());
          document.setTableId(req.getTableId());
          amsEventPublisher.publishEvent(new DocumentLogEvent(document, opEnum, "拆离"));
        });
    updateBatchById(documentList);
    updateSummary(req, type, parentIds);
  }

  /**
   * 更新统计信息
   *
   * @param req
   * @param type
   * @param parentIds
   */
  private void updateSummary(DetachDTO req, DetachEnum type, List<Long> parentIds) {
    ArchiveType archiveType = archiveTypeSvc.getById(req.getArchTypeId());
    if (DetachEnum.BOX.equals(type)) {
      parentIds.forEach(
          id -> {
            amsEventPublisher.publishEvent(
                new SummaryBoxEvent(
                    id,
                    req.getArchTypeId(),
                    archiveType.getBoxTableId(),
                    archiveType.getDocTableId()));
          });
    }

    if (DetachEnum.VOLUME.equals(type)) {
      parentIds.forEach(
          id -> {
            amsEventPublisher.publishEvent(
                new SummaryVolumeEvent(
                    id,
                    req.getArchTypeId(),
                    archiveType.getVolTableId(),
                    archiveType.getDocTableId()));
          });
    }
    if (DetachEnum.PROJ.equals(type)) {
      parentIds.forEach(
          id ->
              amsEventPublisher.publishEvent(
                  new SummaryProjectEvent(
                      id,
                      req.getArchTypeId(),
                      archiveType.getProjTableId(),
                      archiveType.getDocTableId(),
                      archiveType.getVolTableId(),
                      archiveType.getBoxTableId())));
    }
  }

  @Override
  public Boolean cleanTrash(BaseBO req) {
    Set<Long> ids = IdUtil.split(req.getDocIds());
    this.baseMapper.deletion(req.getTableId(), ids);
    return true;
  }

  @Override
  public Boolean recoveryData(BaseBO req) {
    Set<Long> ids = IdUtil.split(req.getDocIds());
    this.baseMapper.recoveryData(req.getTableId(), ids);
    amsEventPublisher.publishEvent(
        new DocumentLogEvent(
            ConvertUtil.toBean(req, Document.class),
            ArchOpEnum.GO_BACK_TO_ARRANGE,
            "从回收站恢复数据到收集库"));
    List<Document> documents = selectListByIds(req);
    documents.forEach(
        item -> {
          item.setArchTypeId(req.getArchTypeId());
          item.setTableId(req.getTableId());
          summary(item);
        });
    return null;
  }

  @Override
  public UploadImportVO importUpload(UploadImportFileDTO req) throws Exception {
    UploadImportVO archUploadVO = new UploadImportVO();
    EfileRecordDTO recordDTO = new EfileRecordDTO();
    recordDTO.setArchTypeId(req.getArchTypeId());
    EfileRecordVO efileRecordVO = efileRecordSvc.uploadTemp(req.getFile(), recordDTO);
    archUploadVO.setFileInfo(efileRecordVO);
    // 查询数据库表头
    ArchiveFieldQueryDTO queryDTO = BeanUtil.copyProperties(req, ArchiveFieldQueryDTO.class);
    List<ArchiveFieldListVO> fields = archiveFieldSvc.selectList(queryDTO);
    List<String> header =
        fields.stream().map(item -> item.getDbFieldName()).collect(Collectors.toList());
    // 读取excel表头,匹配Excel表头
    EasyExcel.read(
            efileRecordVO.getFile(),
            new AnalysisEventListener<Map<Integer, String>>() {
              // 直接使用Map来保存数据
              @Override
              public void invoke(Map<Integer, String> rowData, AnalysisContext context) {}

              @Override
              public void doAfterAllAnalysed(AnalysisContext context) {
                // 所有行都解析完成
                archUploadVO.setTotalCount(context.getTotalCount() - 1);
              }

              @Override
              public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
                log.info("表头数据 excelHead= {}", headMap);
                List<String> fieldHead = headMap.values().stream().collect(Collectors.toList());
                archUploadVO.setHeadExcel(fieldHead);
              }
            })
        .sheet()
        .doRead();
    // 匹配上的字段
    List<String> newFields =
        CollectionUtil.intersection(header, archUploadVO.getHeadExcel()).stream()
            .collect(Collectors.toList());
    archUploadVO.setFields(newFields);
    // 未匹配上字段
    List<String> noFields =
        CollectionUtil.disjunction(archUploadVO.getHeadExcel(), newFields).stream()
            .collect(Collectors.toList());
    archUploadVO.setNoFields(noFields);
    archUploadVO.setDbFields(header);
    return archUploadVO;
  }

  @Override
  public UploadImportDataVO importData(UploadImportDataDTO req) throws Exception {
    UploadImportDataVO dataVO = new UploadImportDataVO();
    try {
      if (StrUtil.isBlank(req.getTableId())) {
        throw BizTipException.instance(
            ErrorCode.ARCHIVE_DATA_EXIST, StrUtil.format("请确定导入档案库，必须参数tableId为空!"));
      }
      ArchiveTypeVO archiveType = archiveTypeSvc.view(req.getArchTypeId());
      // 根据导入的字段进行导入处理,查询数据库表头
      ArchiveFieldQueryDTO queryDTO = BeanUtil.copyProperties(req, ArchiveFieldQueryDTO.class);
      List<ArchiveFieldListVO> fields = archiveFieldSvc.selectList(queryDTO);
      // 过滤掉多余的数据
      if (!FileUtil.exist(req.getPath())) {
        throw BizTipException.instance(ErrorCode.ARCHIVE_DATA_EXIST, StrUtil.format("当前上传文件!"));
      }

      List<String> excelFields =
          req.getRefField().stream()
              .filter(x -> ObjectUtil.isNotEmpty(x.getDbField()))
              .map(RefFieldDTO::getDbField)
              .collect(Collectors.toList());
      List<ArchiveFieldListVO> finalFields =
          fields.stream()
              .filter(x -> excelFields.contains(x.getDbFieldName()))
              .collect(Collectors.toList());

      Long fondsId = SecurityUtil.getFondsId();
      String fondsName = SecurityUtil.getFondsName();
      LoginUser loginUser = SecurityUtil.getLoginUser();
      Long manageStatus = SecurityUtil.getManageStatus();
      // 导入标识
      String didEn =
          ObjectUtil.isEmpty(req.getDid())
              ? null
              : dateCheckCmd.getKeyEn(req.getDid(), finalFields);
      // 导入父标识
      String pidEn =
          ObjectUtil.isEmpty(req.getPid())
              ? null
              : dateCheckCmd.getKeyEn(req.getPid(), finalFields);

      // 校验数据
      dateCheckCmd.checkRepeatDoc(req);

      // 读取Excel信息
      EasyExcel.read(
              req.getPath(),
              new ReadListener<Map<Integer, String>>() {
                Map<Integer, ReadCellData<?>> headMaps;

                /** 临时存储 */
                private List<Map<String, Object>> cachedDataList =
                    ListUtils.newArrayListWithExpectedSize(Constants.DEF_BATCH_SAVE_SIZE * 2);

                @Override
                public void invoke(Map<Integer, String> rowData, AnalysisContext context) {
                  // 读取到的每行数据,其key是以0开始的索引
                  Map<String, Object> rowMap = MapUtil.newHashMap();
                  rowData.forEach(
                      (key, value) -> {
                        ReadCellData cellData = MapUtil.get(headMaps, key, ReadCellData.class);
                        String cnKey =
                            ObjectUtil.isNotEmpty(cellData) ? cellData.getStringValue() : null;
                        String finalCnKey = cnKey;
                        if (ObjectUtil.isNotEmpty(cnKey)) {
                          RefFieldDTO fieldDTO =
                              req.getRefField().stream()
                                  .filter(x -> ObjectUtil.equals(x.getExcelField(), finalCnKey))
                                  .findFirst()
                                  .orElse(null);
                          cnKey = ObjectUtil.isNotEmpty(fieldDTO) ? fieldDTO.getDbField() : null;
                        }
                        if (excelFields.contains(finalCnKey) || excelFields.contains(cnKey)) {
                          rowMap.put(dateCheckCmd.getKeyEn(cnKey, finalFields), value);
                        }
                      });
                  cachedDataList.add(rowMap);
                  if (cachedDataList.size() >= Constants.DEF_BATCH_SAVE_SIZE * 2) {
                    List<Map<String, Object>> tmpList = Lists.newArrayList();
                    tmpList.addAll(cachedDataList);
                    if (ObjectUtil.equal(req.getUpdateArchCode(), 1)) {
                      saveDataDb(
                          req,
                          archiveType,
                          tmpList,
                          fields,
                          fondsId,
                          fondsName,
                          didEn,
                          pidEn,
                          loginUser,
                          manageStatus);
                      dataVO.setSuccessTotal(dataVO.getSuccessTotal() + cachedDataList.size());
                    } else {
                      saveDataDbAdd(
                          req,
                          archiveType,
                          tmpList,
                          fields,
                          fondsId,
                          fondsName,
                          didEn,
                          pidEn,
                          loginUser,
                          manageStatus);
                      dataVO.setSuccessTotal(dataVO.getSuccessTotal() + cachedDataList.size());
                    }
                    // 存储完成清理 list
                    cachedDataList =
                        ListUtils.newArrayListWithExpectedSize(Constants.DEF_BATCH_SAVE_SIZE * 2);
                  }
                }

                @Override
                public void invokeHead(
                    Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
                  headMaps = headMap;
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext context) {
                  List<Map<String, Object>> tmpList = Lists.newArrayList();
                  tmpList.addAll(cachedDataList);
                  if (ObjectUtil.equal(req.getUpdateArchCode(), 1)) {
                    saveDataDb(
                        req,
                        archiveType,
                        tmpList,
                        fields,
                        fondsId,
                        fondsName,
                        didEn,
                        pidEn,
                        loginUser,
                        manageStatus);
                    dataVO.setSuccessTotal(dataVO.getSuccessTotal() + cachedDataList.size());
                  } else {
                    saveDataDbAdd(
                        req,
                        archiveType,
                        tmpList,
                        fields,
                        fondsId,
                        fondsName,
                        didEn,
                        pidEn,
                        loginUser,
                        manageStatus);
                    dataVO.setSuccessTotal(dataVO.getSuccessTotal() + cachedDataList.size());
                  }
                }
              })
          .sheet()
          .doRead();

      try {
        FileUtil.del(req.getPath());
      } catch (Exception e) {
        log.debug("删除临时文件异常:{}", e.getMessage());
      }

    } catch (ReturnDataException ex) {
      throw ex;
    } catch (Exception ex) {
      ex.printStackTrace();
      throw BizTipException.instance(
          ErrorCode.ARCHIVE_DATA_EXIST, StrUtil.format("数据导入异常，异常信息：{}!", ex.getMessage()));
    }

    return dataVO;
  }

  /**
   * 保存数据入库
   *
   * @param req
   * @param datas
   * @param fields
   * @param fondsId
   * @param loginUser
   * @param manageStatus
   */
  @Async
  protected void saveDataDbAdd(
      UploadImportDataDTO req,
      ArchiveTypeVO archiveType,
      List<Map<String, Object>> datas,
      List<ArchiveFieldListVO> fields,
      Long fondsId,
      String fondsName,
      String didEn,
      String pidEn,
      LoginUser loginUser,
      Long manageStatus) {

    // 执行更新关联关系
    List<String> pids =
        ObjectUtil.isEmpty(pidEn)
            ? com.google.common.collect.Lists.newArrayList()
            : datas.stream()
                .map(
                    item -> {
                      return MapUtil.getStr(item, pidEn, null);
                    })
                .collect(Collectors.toList());
    List<Volume> volumes = com.google.common.collect.Lists.newArrayList();
    List<Box> boxs = com.google.common.collect.Lists.newArrayList();
    if (BooleanUtil.isTrue(archiveType.getHaveVol()) && !pids.isEmpty()) {
      LambdaQueryWrapper<Volume> qw = Wrappers.lambdaQuery();
      qw.in(Volume::getDid, pids);
      DynamicTableNameUtil.setTableName(archiveType.getVolTableId());
      IVolumeSvc volumeSvc = SpringUtil.getBean(IVolumeSvc.class);
      volumes = volumeSvc.list(qw);
    }
    if (BooleanUtil.isTrue(archiveType.getHaveBox()) && !pids.isEmpty()) {
      LambdaQueryWrapper<Box> qw = Wrappers.lambdaQuery();
      qw.in(Box::getDid, pids);
      DynamicTableNameUtil.setTableName(archiveType.getBoxTableId());
      IBoxSvc boxSvc = SpringUtil.getBean(IBoxSvc.class);
      boxs = boxSvc.list(qw);
    }

    // 执行数据转换为entity
    List<Volume> finalVolumes = volumes;
    List<Box> finalBoxs = boxs;
    List<Document> documentList =
        datas.stream()
            .map(
                item -> {
                  checkData(item, fields);
                  // 增加数据库数据逻辑判断
                  Document document = BeanUtil.copyProperties(item, Document.class);
                  document.setArchTypeId(req.getArchTypeId());
                  document.setTableId(req.getTableId());
                  document.setFondsId(fondsId);
                  document.setDepartmentId(loginUser.getDeptId());
                  document.setDepartment(loginUser.getDeptName());
                  document.setFillingDeptId(req.getFillingDept());
                  document.setDid(MapUtil.getStr(item, didEn, null));
                  document.setPid(MapUtil.getStr(item, pidEn, null));

                  if (ObjectUtil.isNotEmpty(req.getProjId())) {
                    document.setProjId(req.getProjId());
                    document.setProjCode(req.getProjCode());
                  }
                  if (ObjectUtil.isNotEmpty(req.getVolId())) {
                    document.setVolId(req.getVolId());
                  }
                  document.setManageStatus(manageStatus);
                  document.setStatus(req.getArchStatus());
                  if (ObjectUtil.equals(req.getDefFondsCode(), 1)) {
                    document.setFondsId(fondsId);
                    document.setFondsName(fondsName);
                    document.setFondsCode(SecurityUtil.getFondsCode());
                  }

                  if (!finalVolumes.isEmpty()) {
                    Volume volume =
                        finalVolumes.stream()
                            .filter(x -> ObjectUtil.equals(x.getDid(), document.getPid()))
                            .findFirst()
                            .orElse(null);
                    document.setVolId(ObjectUtil.isEmpty(volume) ? null : volume.getId());
                    document.setVolNo(ObjectUtil.isEmpty(volume) ? null : volume.getVolNo());
                  }

                  if (!finalBoxs.isEmpty()) {
                    Box box =
                        finalBoxs.stream()
                            .filter(x -> ObjectUtil.equals(x.getDid(), document.getPid()))
                            .findFirst()
                            .orElse(null);
                    document.setBoxId(ObjectUtil.isEmpty(box) ? null : box.getId());
                    document.setBoxNo(ObjectUtil.isEmpty(box) ? null : box.getBoxNo());
                  }

                  // 判断成文日期，计算到期时间
                  if (ObjectUtil.isNotEmpty(document.getDocDate())) {
                    document.setExpireYear(
                        retentionCmd.getExpireYear(
                            document.getYear(),
                            document.getRetentionPeriod(),
                            document.getDocDate()));
                  }

                  return document;
                })
            .collect(Collectors.toList());
    // 执行数据转换为entity
    if (!documentList.isEmpty()) {
      DynamicTableNameUtil.setTableName(archiveType.getDocTableId());
      this.saveBatch(documentList, Constants.DEF_BATCH_SAVE_SIZE * 2);
    }
    try {
      if (ObjectUtil.equal(req.getAddArchLog(), 1)) {
        amsEventPublisher.publishEvent(
            new DocumentLogEvent(documentList, ArchOpEnum.BATCH_IMPORT, "通过条目导入创建目录树"));
        amsEventPublisher.publishEvent(
            new DocumentLogEvent(documentList, ArchOpEnum.ADD_ENTITY, "实体增加登记+1份"));
      }
    } catch (Exception e) {
      log.error("通过条目导入创建目录树：{}", e.getMessage());
    }
  }

  /**
   * 保存数据入库
   *
   * @param req
   * @param datas
   * @param fields
   * @param fondsId
   * @param loginUser
   * @param manageStatus
   */
  @Async
  protected void saveDataDb(
      UploadImportDataDTO req,
      ArchiveTypeVO archiveType,
      List<Map<String, Object>> datas,
      List<ArchiveFieldListVO> fields,
      Long fondsId,
      String fondsName,
      String didEn,
      String pidEn,
      LoginUser loginUser,
      Long manageStatus) {

    // 执行更新关联关系
    List<String> pids =
        ObjectUtil.isEmpty(pidEn)
            ? com.google.common.collect.Lists.newArrayList()
            : datas.stream()
                .map(
                    item -> {
                      return MapUtil.getStr(item, pidEn, null);
                    })
                .collect(Collectors.toList());
    List<Volume> volumes = com.google.common.collect.Lists.newArrayList();
    List<Box> boxs = com.google.common.collect.Lists.newArrayList();
    if (BooleanUtil.isTrue(archiveType.getHaveVol()) && !pids.isEmpty()) {
      LambdaQueryWrapper<Volume> qw = Wrappers.lambdaQuery();
      qw.in(Volume::getDid, pids);
      DynamicTableNameUtil.setTableName(archiveType.getVolTableId());
      IVolumeSvc volumeSvc = SpringUtil.getBean(IVolumeSvc.class);
      volumes = volumeSvc.list(qw);
    }
    if (BooleanUtil.isTrue(archiveType.getHaveBox()) && !pids.isEmpty()) {
      LambdaQueryWrapper<Box> qw = Wrappers.lambdaQuery();
      qw.in(Box::getDid, pids);
      DynamicTableNameUtil.setTableName(archiveType.getBoxTableId());
      IBoxSvc boxSvc = SpringUtil.getBean(IBoxSvc.class);
      boxs = boxSvc.list(qw);
    }

    // 执行数据转换为entity
    List<Volume> finalVolumes = volumes;
    List<Box> finalBoxs = boxs;
    // 执行数据转换为entity
    List<Document> documentList =
        datas.stream()
            .map(
                item -> {
                  checkData(item, fields);
                  // 增加数据库数据逻辑判断
                  Document document = BeanUtil.copyProperties(item, Document.class);
                  document.setArchTypeId(req.getArchTypeId());
                  document.setTableId(req.getTableId());
                  document.setFondsId(fondsId);
                  document.setDepartmentId(loginUser.getDeptId());
                  document.setDepartment(loginUser.getDeptName());
                  document.setFillingDeptId(req.getFillingDept());

                  document.setDid(MapUtil.getStr(item, didEn, null));
                  document.setPid(MapUtil.getStr(item, pidEn, null));

                  if (ObjectUtil.isNotEmpty(req.getProjId())) {
                    document.setProjId(req.getProjId());
                    document.setProjCode(req.getProjCode());
                  }
                  if (ObjectUtil.isNotEmpty(req.getVolId())) {
                    document.setVolId(req.getVolId());
                  }
                  document.setStatus(req.getArchStatus());
                  document.setManageStatus(manageStatus);
                  if (ObjectUtil.equals(req.getDefFondsCode(), 1)) {
                    document.setFondsId(fondsId);
                    document.setFondsName(fondsName);
                    document.setFondsCode(SecurityUtil.getFondsCode());
                  }

                  if (!finalVolumes.isEmpty()) {
                    Volume volume =
                        finalVolumes.stream()
                            .filter(x -> ObjectUtil.equals(x.getDid(), document.getPid()))
                            .findFirst()
                            .orElse(null);
                    document.setVolId(ObjectUtil.isEmpty(volume) ? null : volume.getId());
                    document.setVolNo(ObjectUtil.isEmpty(volume) ? null : volume.getVolNo());
                  }

                  if (!finalBoxs.isEmpty()) {
                    Box box =
                        finalBoxs.stream()
                            .filter(x -> ObjectUtil.equals(x.getDid(), document.getPid()))
                            .findFirst()
                            .orElse(null);
                    document.setBoxId(ObjectUtil.isEmpty(box) ? null : box.getId());
                    document.setBoxNo(ObjectUtil.isEmpty(box) ? null : box.getBoxNo());
                  }

                  // 判断成文日期，计算到期时间
                  if (ObjectUtil.isNotEmpty(document.getDocDate())) {
                    document.setExpireYear(
                        retentionCmd.getExpireYear(
                            document.getYear(),
                            document.getRetentionPeriod(),
                            document.getDocDate()));
                  }
                  return document;
                })
            .collect(Collectors.toList());
    // 获取所有导出的档号
    List<String> archCodes =
        documentList.stream().map(Document::getArchCode).collect(Collectors.toList());
    // 查询库里有次档号的数据
    DynamicTableNameUtil.setTableName(req.getTableId());
    LambdaQueryWrapper<Document> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.in(Document::getArchCode, archCodes);
    List<Document> docDbList = Lists.newArrayList();
    if (archCodes.size() != 0) {
      DynamicTableNameUtil.setTableName(req.getTableId());
      docDbList = documentMapper.selectList(queryWrapper);
    }
    // 执行数据转换为entity
    List<Document> finalDocDbList = docDbList;
    List<Document> documentListAll =
        documentList.stream()
            .map(
                item -> {
                  Document document = BeanUtil.copyProperties(item, Document.class);
                  document.setManageStatus(manageStatus);
                  document.setArchTypeId(req.getArchTypeId());
                  document.setTableId(req.getTableId());
                  if (!archCodes.isEmpty()) {
                    List<Document> idObj =
                        finalDocDbList.stream()
                            .filter(doc -> doc.getArchCode().equalsIgnoreCase(item.getArchCode()))
                            .collect(Collectors.toList());
                    if (!idObj.isEmpty()) {
                      document.setId(idObj.get(0).getId());
                    }
                  }
                  return document;
                })
            .collect(Collectors.toList());

    // 查找修改的列表
    List<Document> updateList =
        documentListAll.stream()
            .filter(x -> ObjectUtil.isNotEmpty(x.getId()))
            .collect(Collectors.toList());
    if (!updateList.isEmpty()) {
      DynamicTableNameUtil.setTableName(archiveType.getDocTableId());
      this.saveOrUpdateBatch(updateList, datas.size());
    }
    // 查找新增的列表
    List<Document> saveupdate =
        documentListAll.stream()
            .filter(x -> ObjectUtil.isEmpty(x.getId()))
            .collect(Collectors.toList());
    if (!saveupdate.isEmpty()) {
      DynamicTableNameUtil.setTableName(archiveType.getDocTableId());
      this.saveBatch(saveupdate, datas.size());
    }

    try {
      if (ObjectUtil.equal(req.getAddArchLog(), 1)) {
        amsEventPublisher.publishEvent(
            new DocumentLogEvent(documentListAll, ArchOpEnum.BATCH_IMPORT, "通过条目导入创建目录树"));
        amsEventPublisher.publishEvent(
            new DocumentLogEvent(documentListAll, ArchOpEnum.ADD_ENTITY, "实体增加登记+1份"));
      }
    } catch (Exception e) {
      log.error("通过条目导入创建目录树：{}", e.getMessage());
    }
  }

  /**
   * 校验单元数据
   *
   * @param item
   * @param fields
   */
  private void checkData(Map<String, Object> item, List<ArchiveFieldListVO> fields) {
    for (String key : item.keySet()) {
      String value = MapUtil.getStr(item, key, null);
      ArchiveFieldListVO fieldItem =
          fields.stream()
              .filter(x -> x.getDbFieldKey().equals(StringUtils.camelToUnderline(key)))
              .findFirst()
              .orElse(null);
      if (ObjectUtil.isNotEmpty(fieldItem)) {
        assert fieldItem != null;
        try {
          if (fieldItem.getDbType().equalsIgnoreCase("bigint")
              || fieldItem.getDbType().equalsIgnoreCase("int")) {
            item.replace(key, NumberUtil.isNumber(value) ? value : null);
          } else if (fieldItem.getDbType().equalsIgnoreCase("datetime")
              || fieldItem.getDbType().equalsIgnoreCase("date")) {
            Date d =
                org.apache.commons.lang3.time.DateUtils.parseDate(value, Constants.parsePatterns);

            item.replace(
                key,
                ObjectUtil.isNotEmpty(d)
                    ? DateUtil.format(d, DatePattern.PURE_DATETIME_FORMATTER)
                    : null);
          }
        } catch (Exception e) {
          log.debug("处理格式内容异常:{}", e.getMessage());
        }
      }
    }
  }

  @Override
  public void changeStatus(Document req) {
    List<Document> documents = selectListByIds(req);
    if (ObjectUtil.isEmpty(documents)) {
      return;
    }
    Integer oldStatus = documents.get(0).getStatus();
    documents.forEach(item -> item.setStatus(req.getStatus()));
    updateBatchById(documents);
    // 修改目录数据
    documents.forEach(
        item -> {
          Document old = ConvertUtil.toBean(item, Document.class);
          old.setStatus(oldStatus);
          old.setArchTypeId(req.getArchTypeId());
          old.setTableId(req.getTableId());
          item.setArchTypeId(req.getArchTypeId());
          item.setTableId(req.getTableId());
          amsEventPublisher.publishEvent(
              new DocumentLogEvent(
                  ConvertUtil.toBean(req, Document.class),
                  ArchOpEnum.TURN_NOT_ARCHIVE,
                  "移动数据到不归档库"));
        });
    if (ArchStatusEnum.S0.getCode().equals(req.getStatus())) {
      amsEventPublisher.publishEvent(
          new EsRemoveDataEvent(req.getDocIds(), req.getArchTypeId(), req.getTableId()));
    }
  }

  @Override
  public List<Document> statisticProjByVol(String tableId) {
    return this.baseMapper.statisticProjByVol(tableId);
  }

  @Override
  public List<Document> selectListIgnoreFonds(BaseBO req) {
    return this.baseMapper.selectListIgnoreFonds(req.getTableId(), req.getDocIds());
  }
}
